package com.ideaaedi.plugin.fastcoding.action;

import com.google.common.collect.Maps;
import com.ideaaedi.commonds.exception.ExceptionUtil;
import com.ideaaedi.plugin.fastcoding.model.GetterInfo;
import com.ideaaedi.plugin.fastcoding.util.JdPsiClassUtil;
import com.ideaaedi.plugin.fastcoding.util.JdPsiFileUtil;
import com.ideaaedi.plugin.fastcoding.util.NotifyUtil;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDeclarationStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiType;
import com.intellij.psi.impl.source.PsiParameterImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 调用getter方法
 *
 * @author JustryDeng
 * @since 2021/12/18 9:10:51
 */
@SuppressWarnings("AlibabaUndefineMagicConstant")
public class InvokeGetterAction extends AnAction {
    
    public static final String ACTION_NAME = "Invoke Getter";
    
    @Override
    public void actionPerformed(AnActionEvent event) {
        Project project = event.getData(PlatformDataKeys.PROJECT);
        if (project == null) {
            return;
        }
        try {
            PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(event.getDataContext());
            if (element == null) {
                NotifyUtil.notify(project, ACTION_NAME, "Cannot find element.");
                return;
            }
            PsiMethod psiMethod = PsiTreeUtil.getParentOfType(element,  PsiMethod.class);
            if (psiMethod == null) {
                return;
            }
            generateInvokeGetter(project, psiMethod, element);
        } catch (Exception e) {
            NotifyUtil.notify(project, ACTION_NAME, ExceptionUtil.getStackTraceMessage(e), NotificationType.ERROR);
        }
    }
    
    /**
     * 生成getter调用
     *
     * @param project
     *            项目信息
     * @param psiMethod
     *            当前元素所在方法
     * @param element
     *            当前元素
     */
    private static void generateInvokeGetter(Project project, PsiMethod psiMethod, PsiElement element) {
        if (!(element instanceof PsiParameterImpl) && !(element instanceof PsiLocalVariable)) {
            NotifyUtil.notify(project, ACTION_NAME, "Only support PsiParameterImpl or PsiLocalVariable.");
            return;
        }
        // 获取对应类型的的getter方法
        PsiClass psiClass;
        String paramName;
        int offset;
        String indentation;
        // PsiParameterImpl or PsiLocalVariable
        if (element instanceof PsiParameterImpl) {
            PsiParameterImpl psiParameter = (PsiParameterImpl) element;
            psiClass = PsiTypesUtil.getPsiClass(psiParameter.getType());
            paramName = psiParameter.getName();
            // public Abc m1(Param req) {
            // 参数文本： (Param req)
            PsiElement paramsElement = psiParameter.getParent();
            PsiElement methodElement = paramsElement.getParent();
            // 方法文本： m1(Param req)
            String methodInfoText = methodElement.getText();
            offset = methodElement.getTextRange().getStartOffset() + methodInfoText.indexOf("{") + 1;
            indentation = JdPsiFileUtil.determineIndentationStr(project, psiMethod,  methodElement) + "    ";
        } else {
            PsiLocalVariable psiLocalVariable = (PsiLocalVariable) element;
            /*
             * 获取局部变量所在的（完整语法）"行"
             * 如: 对People people = new People();中的局部变量people进行操作时，获取到
             *     的parentDeclarationStatement就是"People people = new People();"
             */
            PsiElement parentDeclarationStatement = psiLocalVariable.getParent();
            if (!(parentDeclarationStatement instanceof PsiDeclarationStatement)) {
                return;
            }
            psiClass = PsiTypesUtil.getPsiClass(psiLocalVariable.getType());
            paramName = psiLocalVariable.getName();
            offset = parentDeclarationStatement.getTextOffset() + parentDeclarationStatement.getText().length();
            indentation = JdPsiFileUtil.determineIndentationStr(project, psiMethod,  psiLocalVariable.getParent());
        }
        
        List<PsiMethod> methodList = JdPsiClassUtil.extractGetter(psiClass);
        if (methodList.size() == 0) {
            return;
        }
        GetterInfo getterInfo = buildGetInfo(paramName, methodList);
        String appendStr = generateAppendStr(getterInfo, indentation);
        if (appendStr.trim().length() == 0) {
            return;
        }
        // 获取java文件
        PsiFile psiFile = psiMethod.getContainingFile();
        if (!(psiFile instanceof PsiJavaFile)) {
            throw new IllegalArgumentException("curr file is not java file.");
        }
        PsiJavaFile psiJavaFile = (PsiJavaFile)psiFile;
        // 插入getter调用代码
        JdPsiFileUtil.insertContent(project,  psiJavaFile, offset, appendStr);
        // 提交保存文件
        JdPsiFileUtil.commitAndSaveDocument(project, psiJavaFile);
        // 添加导入
        Set<String> returnClassLongNameSet = methodList.stream()
                .flatMap(x -> {
                    PsiType returnType = x.getReturnType();
                    if (returnType == null) {
                        return Stream.empty();
                    }
                    if (JdPsiClassUtil.isPrimitiveType(returnType)) {
                        return Stream.empty();
                    }
                    String canonicalText = returnType.getCanonicalText();
                    if (canonicalText.contains("<")) {
                        return Arrays.stream(canonicalText.split("[,<>]"))
                                .filter(y -> !(JdPsiClassUtil.isBaseType(y) && JdPsiClassUtil.isJavaLangType(returnType)));
                    }
                    if (JdPsiClassUtil.isBaseType(returnType) && JdPsiClassUtil.isJavaLangType(returnType)) {
                        return Stream.empty();
                    }
                    return Stream.of(canonicalText);
                }).collect(Collectors.toSet());
        JdPsiFileUtil.addImport(project, psiJavaFile, returnClassLongNameSet);
    }
    
    /**
     * 创建参数parameter的get信息对象
     *
     * @param paramName
     *            参数名称
     * @param getMethods
     *            parameter的所有get方法
     * @return  get信息对象
     */
    private static GetterInfo buildGetInfo(String paramName, List<PsiMethod> getMethods) {
        GetterInfo info = new GetterInfo();
        info.setLocalVarName(paramName);
        info.setGetterMethodList(getMethods);
        Map<String, PsiMethod> nameToMethodMaps = Maps.newHashMap();
        for (PsiMethod getMethod : getMethods) {
            String fieldName = null;
            if (getMethod.getName().startsWith("is")) {
                fieldName = getMethod.getName().substring(2);
            } else if (getMethod.getName().startsWith("get")) {
                fieldName = getMethod.getName().substring(3);
            }
            if (fieldName != null) {
                nameToMethodMaps.put(StringUtils.uncapitalize(fieldName), getMethod);
            }
        }
        info.setFieldGetterMap(nameToMethodMaps);
        return info;
    }

    /**
     * 生成要追加的字符串
     *
     * @param getterInfo
     *            get方法信息模型
     * @param indentation
     *            每行向右缩进时（，左侧填充的字符串）
     * @return  要追加的字符串
     */
    private static String generateAppendStr(GetterInfo getterInfo, String indentation) {
        final String indentationStr = indentation == null ? "" : indentation;
        StringBuilder sb = new StringBuilder(64);
        Map<String, PsiMethod> nameToMethodMap = getterInfo.getFieldGetterMap();
        if (nameToMethodMap == null) {
            return sb.toString();
        }
        String paramName = getterInfo.getLocalVarName();
        if (nameToMethodMap.size() != 0) {
            nameToMethodMap.forEach((k, v) -> {
                PsiType returnType = v.getReturnType();
                if (returnType == null) {
                    return;
                }
                String type = returnType.getPresentableText();
                sb.append("\n")
                        .append(indentationStr)
                        .append(type)
                        .append(" ")
                        .append(k)
                        .append(" = ")
                        .append(paramName)
                        .append(".")
                        .append(v.getName())
                        .append("();");
            });
        }
        return sb.toString();
    }
}
